/****************************************************************************
 *
 * Copyright (C) 2003-2008 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse.dataset.tippingpoint;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Vector;

import java.sql.Timestamp;

import nfse.NetFSE;
import nfse.NetFSERecord;
import nfse.NetFSEUtil;
import nfse.gui.record.FlexRecord;
import nfse.gui.record.FlexRecordField;

/*******************************************************************************
 * This class is used to parse and store TippingPoint records.
 * 
 * @author
 * 
 * This code is the property of Los Alamos National Laboratory and the
 * University of California, Copyright 2005
 */
public class TippingPointRecord extends NetFSERecord {

    /***************************************************************************
     * Defines what version of the log format was used to generate this object
     */
    public int type = -1;

    /***************************************************************************
     * Flag indicating if this is a Peer-to-peer record
     */
    public boolean p2p = false;

    /***************************************************************************
     * Text string represnting the protocol for this block (e.g. "TCP", "UDP")
     */
    public String proto = "";

    /***************************************************************************
     * The TCP source port for this block represented
     */
    public int srcPort = -1;

    /***************************************************************************
     * The destination IP address for this block represented as a dot quad (e.g.
     * 1.2.3.4)
     */
    public String dstAddr = "";

    /***************************************************************************
     * The TCP destination port for this block represented
     */
    public int dstPort = -1;

    /***************************************************************************
     * The destination IP address for this block represented as a 32-bit integer
     */
    public long dstIP = -1;

    /***************************************************************************
     * Text represntation of the version of the sensor that generated this block
     */
    public String version = "";

    /***************************************************************************
     * The time when the block took place
     */
    public Timestamp ts1 = null;

    /***************************************************************************
     * The time when the log was generated
     */
    public Timestamp ts2 = null;

    /***************************************************************************
     * Text description of the sensor that generated this block
     */
    public String sensorName = "";

    /***************************************************************************
     * Not sure what this value represents
     */
    public long seqNum = -1;

    /***************************************************************************
     * The type of action that was taken
     */
    public String action = "";

    /***************************************************************************
     * A label indicating the threat level associated with this event
     */
    public String severity = "";

    /***************************************************************************
     * Not sure what this value represents
     */
    public String policyUUID = "";

    /***************************************************************************
     * Identifier for this alert signature
     */
    public int alertID = -1;

    /***************************************************************************
     * Type of this alert signature
     */
    public String alertType = "";

    /***************************************************************************
     * Description of this alert signature
     */
    public String alertDesc = "";
    
    public static void main(String[] args) {
        TippingPointRecord rec = new TippingPointRecord();
        rec.parse(args, rec);
    }
    
    public boolean isAlert() {
        return true;
    }
    
    public String getAlertMessage() {
        return this.alertDesc;
    }

    public FlexRecord getFlexRecord() {
        FlexRecord temp = new FlexRecord();
        temp.values = new String[9];
        int idx = -1;
        temp.values[++idx] = this.ts1.toString();
        temp.values[++idx] = this.getSrcAddr();
        if (this.srcPort == -1)
            temp.values[++idx] = "";
        else
            temp.values[++idx] = this.srcPort + "";
        temp.values[++idx] = this.dstAddr;
        if (this.dstPort == -1)
            temp.values[++idx] = "";
        else
            temp.values[++idx] = this.dstPort + "";
        temp.values[++idx] = proto + "";
        temp.values[++idx] = this.action + "";
        temp.values[++idx] = this.alertDesc + "";
        temp.values[++idx] = this.severity + "";
        return temp;
    }
    
    public Timestamp getStartTS() {
        return ts1;
    }
    
    

    public void setDstPort(int dstPort) {
        this.dstPort = dstPort;
    }

    public long getDstIP() {
        return dstIP;
    }

    public int getProtocol() {
        return NetFSEUtil.getProtocolValue(proto);
    }

    public int getDstPort() {
        return dstPort;
    }
    
    public int[] getIndexedFields() {
        int[] temp =  {NetFSE.FIELD_START_TS, NetFSE.FIELD_DST_IP, NetFSE.FIELD_SRC_IP, NetFSE.FIELD_DST_PORT };
        return temp;
    }

    public FlexRecordField[] getFlexRecordFields() {
        FlexRecordField[] temp = new FlexRecordField[9];
        int idx = -1;
        temp[++idx] = new FlexRecordField("Start Time", "startts", FlexRecordField.TYPE_STRING,
                FlexRecordField.SUB_TYPE_TIMESTAMP);
        temp[++idx] = new FlexRecordField("Src IP", "srcip", FlexRecordField.TYPE_STRING,
                FlexRecordField.SUB_TYPE_IP_ADDR);
        temp[++idx] = new FlexRecordField("Src Port", "srcport", FlexRecordField.TYPE_INT,
                FlexRecordField.SUB_TYPE_PORT);
        temp[++idx] = new FlexRecordField("Dst IP", "dstip", FlexRecordField.TYPE_STRING,
                FlexRecordField.SUB_TYPE_IP_ADDR);
        temp[++idx] = new FlexRecordField("Dst Port", "dstport", FlexRecordField.TYPE_INT,
                FlexRecordField.SUB_TYPE_PORT);
        temp[++idx] = new FlexRecordField("Protocol", "protocol", FlexRecordField.TYPE_STRING,
                FlexRecordField.SUB_TYPE_NONE);
        temp[++idx] = new FlexRecordField("Action", "action", FlexRecordField.TYPE_STRING, FlexRecordField.SUB_TYPE_NONE);
        temp[++idx] = new FlexRecordField("Description", "description", FlexRecordField.TYPE_STRING, FlexRecordField.SUB_TYPE_NONE);
        temp[++idx] = new FlexRecordField("Severity", "severity", FlexRecordField.TYPE_INT,
                FlexRecordField.SUB_TYPE_NONE);
        return temp;
    }
    
    /***************************************************************************
     * Given a raw TippingPoint record, parse according to the 3rd log format.
     * 
     * @param line
     *            The log to parse into an object
     * @return The data structure for this log record or null on error
     */
    private static TippingPointRecord generateV3(String line, int sensor) {
        try {
            TippingPointRecord tp = new TippingPointRecord();
            String[] tokens = line.split("\t");
            tp.setSensorID(sensor);
            tp.setInitialString(line);
            
            //System.out.println("# tokens = " + tokens.length);
            
            // if (tokens.length == 16) {
            // return generateV4(line, tokens);
            // }

            int offset = 0;
            if (tokens.length == 20) {
                offset = 1;
            }
            else if (tokens.length != 19) {
                return null;
            }

            String[] header = tokens[0+offset].split(" ");
            if (header[header.length - 1].trim().equals("P2P")) {
                tp.p2p = true;
            }

            // Timestamp format is YYYYMMDDTHHMMSS+_TZ_
            String tsStr = tokens[14+offset].substring(0, 4) + "-";
            tsStr += tokens[14+offset].substring(4, 6) + "-";
            tsStr += tokens[14+offset].substring(6, 8) + " ";
            tsStr += tokens[14+offset].substring(9, 11) + ":";
            tsStr += tokens[14+offset].substring(11, 13) + ":";
            tsStr += tokens[14+offset].substring(13, 15);
            tp.ts1 = Timestamp.valueOf(tsStr);

            // Timestamp format is YYYYMMDDTHHMMSS+_TZ_
            //tsStr = tokens[2+offset].substring(0, 4) + "-";
            //tsStr += tokens[2+offset].substring(4, 6) + "-";
            //tsStr += tokens[2+offset].substring(6, 8) + " ";
            //tsStr += tokens[2+offset].substring(9, 11) + ":";
            //tsStr += tokens[2+offset].substring(11, 13) + ":";
            //tsStr += tokens[2+offset].substring(13, 15);
            //tp.ts2 = Timestamp.valueOf(tsStr);

            // Process the ip:port pairs
            String[] tokens2 = tokens[12+offset].split(":");
            tp.setSrcAddr(tokens2[0]);
            tp.setSrcIP(Long.parseLong(NetFSEUtil.convertIPtoHex(tp.getSrcAddr()), 16));
            tp.srcPort = Integer.parseInt(tokens2[1]);
            tokens2 = tokens[13+offset].split(":");
            tp.dstAddr = tokens2[0];
            tp.dstIP = Long.parseLong(NetFSEUtil.convertIPtoHex(tp.dstAddr), 16);
            tp.dstPort = Integer.parseInt(tokens2[1]);

            tp.action = tokens[6+offset].trim();
            tp.severity = tokens[7+offset].trim();
            tp.proto = tokens[11+offset].trim();
            tp.sensorName = tokens[3+offset];
            tp.type = 3;

            tokens2 = tokens[9+offset].split(":");
            tp.alertID = Integer.parseInt(tokens2[0].trim());
            tp.alertType = tokens2[1].trim();
            tp.alertDesc = tokens2[2].trim();
            tp.policyUUID = tokens[8+offset];

            return tp;
        } catch (Exception E) {
            if (line.indexOf("Nimda Attack") < 0) {
                // Only show error info for non-Nimda blocks
                System.out.println("Error line: '" + line + "'");
                E.printStackTrace();
            }
        }
        return null;
    }

    /***************************************************************************
     * Given a raw TippingPoint record, parse according to the 4th log format.
     * 
     * @param tokens
     *            The log to parsed into fields
     * @return The data structure for this log record or null on error
     */
    private static TippingPointRecord generateV4(String line, String[] tokens, int sensor) {
        try {
            TippingPointRecord tp = new TippingPointRecord();
            tp.setSensorID(sensor);

            tp.type = 4;

            // Timestamp format is YYYYMMDDTHHMMSS+_TZ_
            tp.ts1 = new Timestamp(Long.parseLong(tokens[15]));
            tp.ts2 = tp.ts1;

            tp.setSrcAddr(tokens[6]);
            tp.setSrcIP(Long.parseLong(NetFSEUtil.convertIPtoHex(tp.getSrcAddr()), 16));
            tp.srcPort = Integer.parseInt(tokens[7]);
            tp.dstAddr = tokens[8];
            tp.dstIP = Long.parseLong(NetFSEUtil.convertIPtoHex(tp.dstAddr), 16);
            tp.dstPort = Integer.parseInt(tokens[9]);

            tp.action = "Block";
            tp.severity = "";
            tp.proto = tokens[5].trim();
            tp.sensorName = tokens[13].trim();

            tp.alertID = 0;
            tp.alertType = "";
            tp.alertDesc = "";
            tp.policyUUID = "";

            return tp;
        } catch (Exception E) {
            if (line.indexOf("Nimda Attack") < 0) {
                // Only show error info for non-Nimda blocks
                System.out.println("Error line: '" + line + "'");
                E.printStackTrace();
            }
        }
        return null;
    }

    /***************************************************************************
     * Given a raw TippingPoint record, parse according to the 1st or 2nd log
     * format.
     * 
     * @param line
     *            The log to parse into an object
     * @return The data structure for this log record or null on error
     */
    public TippingPointRecord generate(String line, int sensor) {
        try {
            TippingPointRecord tp = new TippingPointRecord();
            tp.setTypeID(typeID);
            tp.setTypeName(typeName);
            tp.setInitialString(line);
            line = line.replace('\'', ' ').replace(',', ' ').replace('"', ' ');
            String[] tokens = line.split("\\^I");

            //System.out.println("# tokens = " + tokens.length);
            
            // If there is only one token then the record is in the 3rd log
            // format
            if (tokens.length == 1)
                return generateV3(line, sensor);

            if (tokens[0].indexOf("tipping-ipa") == -1) {
                // Return null for any logs from SMS (i.e. not from the IPA)
                return null;
            } else {
                for (int i = 0; i < tokens.length; i++)
                    tokens[i] = tokens[i].trim();
            }
            
            
            
            tp.setSensorID(sensor);
            if (tokens.length == 19) { // The 2nd syslog format
                
                tp.type = 2;
                tp.version = tokens[1];

                // Process the alert timestamp string
                String alertTSStr = tokens[2];
                int year = Integer.parseInt(alertTSStr.substring(0, 4));
                int month = Integer.parseInt(alertTSStr.substring(4, 6));
                int day = Integer.parseInt(alertTSStr.substring(6, 8));
                int hour = Integer.parseInt(alertTSStr.substring(9, 11));
                int minute = Integer.parseInt(alertTSStr.substring(11, 13));
                int second = Integer.parseInt(alertTSStr.substring(13, 15));
                tp.ts1 = Timestamp.valueOf(year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second);

                tp.sensorName = (tokens[3].split(" /"))[0].trim();
                tp.seqNum = Long.parseLong(tokens[4]);
                tp.action = tokens[6];
                tp.severity = tokens[7];
                tp.policyUUID = tokens[8];
                tp.proto = tokens[11];

                String[] tokens2 = tokens[9].split(": ");

                // Some tokens have three values, others only two
                if (tokens2.length == 3) {
                    tp.alertID = Integer.parseInt(tokens2[0]);
                    tp.alertType = tokens2[1];
                    tp.alertDesc = tokens2[2];
                } else if (tokens2.length == 2) {
                    tp.alertID = Integer.parseInt(tokens2[0]);
                    tp.alertDesc = tokens2[1];
                } else {
                    throw (new Exception("Bad alert info: '" + line + "'"));
                }

                // Process the ip:port pairs for source and destination
                String[] src = tokens[12].split(":");
                String[] dst = tokens[13].split(":");
                tp.setSrcAddr(src[0]);
                tp.srcPort = Integer.parseInt(src[1]);
                tp.dstAddr = dst[0];
                tp.dstPort = Integer.parseInt(dst[1]);
                tp.setSrcIP(Long.parseLong(NetFSEUtil.convertIPtoHex(tp.getSrcAddr()), 16));
                tp.dstIP = Long.parseLong(NetFSEUtil.convertIPtoHex(tp.dstAddr), 16);

                return tp;
            } else if (tokens.length == 14) { // The 1st syslog format
                tp.type = 1;
                String[] tokens2 = tokens[0].split(" ");

                // Remove extra whitespace from the array
                Vector<String> temp = new Vector<String>();
                for (int i = 0; i < tokens2.length; i++) {
                    if (tokens2[i].trim().length() > 0)
                        temp.addElement(tokens2[i]);
                }
                tokens2 = new String[temp.size()];
                for (int i = 0; i < tokens2.length; i++) {
                    tokens2[i] = (String) temp.elementAt(i);
                }

                if (tokens2.length == 10) {

                    int month = NetFSE.getMonthValue(tokens2[0]);
                    int day = Integer.parseInt(tokens2[1]);
                    GregorianCalendar now = new GregorianCalendar();
                    now.setTimeInMillis(System.currentTimeMillis());
                    int year = now.get(Calendar.YEAR);
                    tp.ts1 = java.sql.Timestamp.valueOf(year + "-" + month + "-" + day + " " + tokens2[2]);
                    tp.sensorName = tokens2[3];
                    tp.action = tokens2[5];
                    month = NetFSE.getMonthValue(tokens2[6]);
                    day = Integer.parseInt(tokens2[7]);

                    tp.ts2 = java.sql.Timestamp.valueOf(year + "-" + month + "-" + day + " " + tokens2[8]);
                    tp.action = tokens2[9];

                    // Misc fields; some are unused and commented out
                    // String val1 = tokens[1];
                    // String val2 = tokens[2];
                    // String category = tokens[3];
                    // String val3 = tokens[4];
                    tp.proto = tokens[7];
                    // String val4 = tokens[10];
                    // String val5 = tokens[11];
                    // String val6 = tokens[12];
                    // String val7 = tokens[13];

                    tokens2 = tokens[5].split(": ");

                    // Some tokens have three values, others only two
                    if (tokens2.length == 2) {
                        tp.alertID = Integer.parseInt(tokens2[0]);
                        tp.alertType = "";
                        tp.alertDesc = tokens2[1];
                    } else if (tokens2.length == 3) {
                        tp.alertID = Integer.parseInt(tokens2[0]);
                        tp.alertType = tokens2[1];
                        tp.alertDesc = tokens2[2];
                    }

                    // Process the ip:port pairs for source and destination
                    tp.setSrcAddr(tokens[8].substring(0, tokens[8].indexOf(':')));
                    tp.setSrcIP(Long.parseLong(NetFSEUtil.convertIPtoHex(tp.getSrcAddr()), 16));
                    tp.srcPort = Integer.parseInt(tokens[8].substring(tokens[8].indexOf(":") + 1));
                    tp.dstAddr = tokens[9].substring(0, tokens[9].indexOf(':'));
                    tp.dstIP = Long.parseLong(NetFSEUtil.convertIPtoHex(tp.dstAddr), 16);
                    tp.dstPort = Integer.parseInt(tokens[9].substring(tokens[9].indexOf(":") + 1));

                    return tp;
                } else {
                    System.err.println("Invalid number of header fields: " + tokens2.length);
                }
            }

        } catch (Exception E) {
            E.printStackTrace();
        }
        return null;
    }

    public void setDstIP(long dstIP) {
        this.dstIP = dstIP;
        this.dstAddr = NetFSEUtil.convertLongToIP(dstIP);
    }

    public String getCreateFieldTableSQL(String table) {
        StringBuffer temp = new StringBuffer("create table " + table + " (ID bigint, ");
        temp.append("srcip inet,");
        temp.append("srcport int,");
        temp.append("dstip inet,");
        temp.append("dstport int,");
        temp.append("action varchar(50),");
        temp.append("alerttype varchar(50),");
        temp.append("alertdesc varchar(50),");
        temp.append("protocol int,");
        temp.append("severity varchar(10),");
        temp.append("startts timestamp");
        temp.append(")");
        return temp.toString();
    }
    
    //public boolean hasBinary() {
     //   return true;
    //}
    
    public String getInsertFieldTableSQL(int id, String table) {
        StringBuffer temp = new StringBuffer("insert into " + table + " values (" + id + ",'");
        temp.append(getSrcIP() + "',");
        temp.append(srcPort + ",'");
        temp.append(dstIP + "',");
        temp.append(dstPort + ",");
        temp.append("'" + this.action + "',");
        temp.append("'" + this.alertType + "',");
        temp.append("'" + this.alertDesc + "',");
        temp.append("" + NetFSEUtil.getProtocolValue(this.proto) + ",");
        temp.append("'" + this.severity + "',");
        temp.append("'" + this.ts1 + "'");
        temp.append(")");
        return temp.toString();
    }

}